Skip to main content

Golang 101

Starting main functions

  • PITFALL: package needs to be main (even if it is under a seperate directory)
    • This will make the build from that directory
  • file name needs to be main.go

Defer

  • A defer statement defers the execution of a function until the surrounding function returns.
  • The deferred call's arguments are evaluated immediately, but the function call is not executed until the surrounding function returns.

Serialization

I have a struct like this:

type Result struct {
Data MyStruct \`json:"data,omitempty"\`
Status string \`json:"status,omitempty"\`
Reason string \`json:"reason,omitempty"\`
}

But even if the instance of MyStruct is entirely empty (meaning, all values are default), it's being serialized as:

"data":{}

Pointers

Remember Go functions are pass by values ad are made copies hence the reason we use pointers to pass the references

package functions
import "fmt"
type Data struct {
name string
age int
}

func (e Data) changeAge(newAge int) {
e.age = newAge
}

func (e \*Data) changeAge2(newAge int) { // will work
e.age = newAge
}

func TestPointers() {
fmt.Println("Testing pointers")
d := Data{"Mel", 20}
fmt.Println(d)
d.changeAge(30) // will not work
fmt.Println(d)
d.age = 30 // will work seems pointers are mostly needed on passing through parameters
fmt.Println(d)
}

Variable iteration

Be wary on using vars on loops see the below

//consider the below
for dir := getFromLoop(){
doSomething(dir)
}
  • by the time doSomething executes it might have dir will have final value from the loop, and execute on doSomething for the final copy multiple times

Bare return in Go

  • The below will return food and menu even if we just declare return since they match the named return parameters
func testMe()(food, menu int){
food := getFood()
menu := getMenu()
return

Channels

package routines
import (
"fmt"
"time"
)

func Spin() {
fmt.Println("STARTING")
go spinner(100 \* time.Millisecond)
fibN := fib(45)
fmt.Println("DONE", fibN)
}

func spinner(delay time.Duration) {
for {
for \_, r := range \`-/|\\\` {
fmt.Printf("\\r%c", r)
time.Sleep(delay)
}
}
}

func fib(x int) int {
if x < 2 {
return x
}

return fib(x-1) + fib(x-2)
}

Semaphore

  • Use Mutex for semaphore
  • RWMutex - for multiple reads and one write

data types (unsigned vs signed)

  • int8, int16, int32, int64
  • uint8, uint16, uint32, uint64 //unsigned if not planning to use in arithmetic
  • rune is int32 and means unicode code point
  • overflowing the int will result into back to 0
  • The reason we explicitly dictate the size is that different compilers translate int with different assumptions even on identical hardware

Data types:

  • Basic - int, strings, booleans
  • Aggregate - struct, arrays
  • Reference - pointers, slices, maps, functions, channels
  • Interface

Strings

Are defaulted to UTF-8

func testStringFunctions() {
tester := "Hello World"
fmt.Println("Testing String functions:")
fmt.Println(tester)
fmt.Println(len(tester))
fmt.Println(tester\[0 : \])
fmt.Println(tester\[0 : 5\])
}

Raw String

var hel = \`sdfafa asdf aad
as
dfafa
.. as
as
dfaf\`
fmt.Print(hel)

Key Packages with String

  • strings - string manipulation
  • strconv - made to convert boolean integer floats to and fro strings
  • strconv.Itoa - int to String
  • strconv.Atoi - string to int
  • unicode - isDigit, isLetter, isUpper, isLower, ToUpper, ToLower

Elegant Constant incremented:

const (
Ok HTTPCode = 200
NotFound HTTPCode = 404
ErrorCode = iota + 900000 // iota is resolved in the number of constants so it will be 900003
ErrorCodeTest
ErrorCodeFour
)
const(
Number0 = iota + 80000
Number1 // 800001
Number2
)

Slices

  • Arrays declared with a fix sizes are arrays
  • Without sizes are slices which are more flexible and changeable

Make command creates an array and returns full slice

Maps

  1. Have distinct keys
func TestMap() {
fmt.Println("==== Testing Mapping function")
mapper := map\[string\]int{
"Charlie": 11,
"Amy": 21,
}

fmt.Println(mapper)
for name := range mapper {
fmt.Println(name)
}

fmt.Println("==== Testing Duplicate Key")
mapper\["Charlie"\] = 29 // overrides existing key as other languages
fmt.Println(mapper)
if age, ok := mapper\["Charlie"\]; ok {
fmt.Println("Charlie's age is ", age)
}
\_, ok := mapper\["New Person"\];
if !ok {
fmt.Println(" not found in map");
}

Iterating over a MAP/SLICE:

// index and value
for i, v := range slice {}

// index only
for i := range slice {}

// value only
for \_, v := range slice {}

Iterate over a map:

// key and value
for key, value := range theMap {}
// key only
for key := range theMap {}

// value only
for \_, value := range theMap {}

GoRoutines

package main
import (
"fmt"
"time"
)
func hello() {
fmt.Println("Hello world goroutine")
}

func main() {
go hello()
time.Sleep(1 \* time.Second)
fmt.Println("main function")
}
  • When a new Goroutine is started, the goroutine call returns immediately. Unlike functions, the control does not wait for the Goroutine to finish executing. The control returns immediately to the next line of code after the Goroutine call and any return values from the Goroutine are ignored.
  • The main Goroutine should be running for any other Goroutines to run. If the main Goroutine terminates then the program will be terminated and no other Goroutine will run.
  • Go is Concurrent not parallel (more cores on the other hand will help with the concurrency)

Another Example of concurrency

package main
import (
"fmt"
"time"
)

func numbers() {
for i := 1; i <= 5; i++ {
time.Sleep(250 \* time.Millisecond)
fmt.Printf("%d ", i)
}
}

func alphabets() {
for i := 'a'; i <= 'e'; i++ {
time.Sleep(400 \* time.Millisecond)
fmt.Printf("%c ", i)
}
}

func main() {
go numbers()
go alphabets()
time.Sleep(3000 \* time.Millisecond)
fmt.Println("main terminated")
}

  • you can make use of channels to store the results of a Goroutine